//------------------------------------------------------------------------------
// <copyright file="XmlAttributeCollection.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------


namespace System.Xml {
    using System;
    using System.Collections;
    using System.Diagnostics;
    
    // Represents a collection of attributes that can be accessed by name or index.
    public sealed class XmlAttributeCollection: XmlNamedNodeMap, ICollection {
        internal XmlAttributeCollection( XmlNode parent ): base( parent ) {
        }

        // Gets the attribute with the specified index.
        [System.Runtime.CompilerServices.IndexerName ("ItemOf")]
        public XmlAttribute this[ int i ] {
            get { 
                try {
                    return (XmlAttribute)nodes[i];
                } catch ( ArgumentOutOfRangeException ) {
                    throw new IndexOutOfRangeException(Res.GetString(Res.Xdom_IndexOutOfRange));
                }
            }
        }

        // Gets the attribute with the specified name.
        [System.Runtime.CompilerServices.IndexerName ("ItemOf")]
        public XmlAttribute this[ string name ]
        {
            get {
                int hash = XmlName.GetHashCode(name);
                
                for (int i = 0; i < nodes.Count; i++) {
                    XmlAttribute node = (XmlAttribute) nodes[i];

                    if (hash == node.LocalNameHash
                        && name == node.Name )
                    {
                        return node;
                    }
                }

                return null;
            }
        }

        // Gets the attribute with the specified LocalName and NamespaceUri.
        [System.Runtime.CompilerServices.IndexerName ("ItemOf")]
        public XmlAttribute this[ string localName, string namespaceURI ]
        {
            get {
                int hash = XmlName.GetHashCode(localName);
                
                for (int i = 0; i < nodes.Count; i++) {
                    XmlAttribute node = (XmlAttribute) nodes[i];

                    if (hash == node.LocalNameHash
                        && localName == node.LocalName 
                        && namespaceURI == node.NamespaceURI)
                    {
                        return node;
                    }
                }

                return null;
            }
        }

        internal int FindNodeOffset( XmlAttribute node ) {
            for (int i = 0; i < nodes.Count; i++) {
                XmlAttribute tmp = (XmlAttribute) nodes[i];

                if (tmp.LocalNameHash == node.LocalNameHash
                    && tmp.Name == node.Name 
                    && tmp.NamespaceURI == node.NamespaceURI )
                {
                    return i;
                }
            }
            return -1;
        }

        internal int FindNodeOffsetNS(XmlAttribute node) {
            for (int i = 0; i < nodes.Count; i++) {
                XmlAttribute tmp = (XmlAttribute) nodes[i];
                if (tmp.LocalNameHash == node.LocalNameHash
                    && tmp.LocalName == node.LocalName 
                    && tmp.NamespaceURI == node.NamespaceURI) {
                    return i;
                }
            }
            return -1;
        }

        // Adds a XmlNode using its Name property
        public override XmlNode SetNamedItem(XmlNode node) {
            if (node != null && !(node is XmlAttribute))
                throw new ArgumentException(Res.GetString(Res.Xdom_AttrCol_Object));

            int offset = FindNodeOffset( node.LocalName, node.NamespaceURI );
            if (offset == -1) {
                return InternalAppendAttribute( (XmlAttribute) node );
            }
            else {
                XmlNode oldNode = base.RemoveNodeAt( offset );
                InsertNodeAt( offset, node );
                return oldNode;
            }
        }

        // Inserts the specified node as the first node in the collection.
        public XmlAttribute Prepend( XmlAttribute node ) {
            if (node.OwnerDocument != null && node.OwnerDocument != parent.OwnerDocument)
                throw new ArgumentException(Res.GetString(Res.Xdom_NamedNode_Context));

            if (node.OwnerElement != null)
                Detach( node );

            RemoveDuplicateAttribute( node );

            InsertNodeAt( 0, node );
            return node;
        }

        // Inserts the specified node as the last node in the collection.
        public XmlAttribute Append(XmlAttribute node) {
            XmlDocument doc = node.OwnerDocument;
            if (doc == null || doc.IsLoading == false) {
                if (doc != null && doc != parent.OwnerDocument) {
                    throw new ArgumentException(Res.GetString(Res.Xdom_NamedNode_Context));
                }
                if (node.OwnerElement != null) {
                    Detach(node);
                }
                AddNode(node);
            }
            else {
                base.AddNodeForLoad(node, doc);
                InsertParentIntoElementIdAttrMap(node);
            }
            return node; 
        }

        // Inserts the specified attribute immediately before the specified reference attribute.
        public XmlAttribute InsertBefore( XmlAttribute newNode, XmlAttribute refNode ) {
            if ( newNode == refNode )
                return newNode;

            if (refNode == null)
                return Append(newNode);

            if (refNode.OwnerElement != parent)
                throw new ArgumentException(Res.GetString(Res.Xdom_AttrCol_Insert));

            if (newNode.OwnerDocument != null && newNode.OwnerDocument != parent.OwnerDocument)
                throw new ArgumentException(Res.GetString(Res.Xdom_NamedNode_Context));

            if (newNode.OwnerElement != null)
                Detach( newNode );

            int offset = FindNodeOffset( refNode.LocalName, refNode.NamespaceURI );
            Debug.Assert( offset != -1 ); // the if statement above guarantees that the ref node is in the collection

            int dupoff = RemoveDuplicateAttribute( newNode );
            if ( dupoff >= 0 && dupoff < offset )
                offset--;
            InsertNodeAt( offset, newNode );

            return newNode;
        }

        // Inserts the specified attribute immediately after the specified reference attribute.
        public XmlAttribute InsertAfter( XmlAttribute newNode, XmlAttribute refNode ) {
            if ( newNode == refNode )
                return newNode;

            if (refNode == null)
                return Prepend(newNode);

            if (refNode.OwnerElement != parent)
                throw new ArgumentException(Res.GetString(Res.Xdom_AttrCol_Insert));

            if (newNode.OwnerDocument != null && newNode.OwnerDocument != parent.OwnerDocument)
                throw new ArgumentException(Res.GetString(Res.Xdom_NamedNode_Context));

            if (newNode.OwnerElement != null)
                Detach( newNode );

            int offset = FindNodeOffset( refNode.LocalName, refNode.NamespaceURI );
            Debug.Assert( offset != -1 ); // the if statement above guarantees that the ref node is in the collection

            int dupoff = RemoveDuplicateAttribute( newNode );
            if ( dupoff >= 0 && dupoff < offset )
                offset--;
            InsertNodeAt( offset+1, newNode );

            return newNode;
        }

        // Removes the specified attribute node from the map.
        public XmlAttribute Remove( XmlAttribute node ) {
            int cNodes = nodes.Count;
            for (int offset = 0; offset < cNodes; offset++) {
                if (nodes[offset] == node) {
                    RemoveNodeAt( offset );
                    return node;
                }
            }
            return null;
        }

        // Removes the attribute node with the specified index from the map.
        public XmlAttribute RemoveAt( int i ) {
            if (i < 0 || i >= Count)
                return null;

            return(XmlAttribute) RemoveNodeAt( i );
        }

        // Removes all attributes from the map.
        public void RemoveAll() {
            int n = Count;
            while (n > 0) {
                n--;
                RemoveAt( n );
            }
        }

        void ICollection.CopyTo(Array array, int index) {
            for (int i=0, max=Count; i<max; i++, index++)
                array.SetValue(nodes[i], index);
        }

        bool ICollection.IsSynchronized {
            get { return false; }
        }

        object ICollection.SyncRoot {
            get { return this; }
        }

        int ICollection.Count {
            get { return base.Count; }
        }
        
        public void CopyTo(XmlAttribute[] array, int index) {
            for (int i=0, max=Count; i<max; i++, index++)
                array[index] = (XmlAttribute)(((XmlNode)nodes[i]).CloneNode(true));
        }        

        internal override XmlNode AddNode( XmlNode node ) {
            //should be sure by now that the node doesn't have the same name with an existing node in the collection
            RemoveDuplicateAttribute( (XmlAttribute)node );
            XmlNode retNode = base.AddNode( node );
            Debug.Assert( retNode is XmlAttribute );
            InsertParentIntoElementIdAttrMap( (XmlAttribute) node );
            return retNode;
        }            

        internal override XmlNode InsertNodeAt( int i, XmlNode node ) {
            XmlNode retNode = base.InsertNodeAt(i, node);
            InsertParentIntoElementIdAttrMap( (XmlAttribute)node );
            return retNode;
        }
        
        internal override XmlNode RemoveNodeAt( int i ) {
            //remove the node without checking replacement
            XmlNode retNode = base.RemoveNodeAt( i );   
            Debug.Assert(retNode is XmlAttribute);
            RemoveParentFromElementIdAttrMap( (XmlAttribute) retNode );
            // after remove the attribute, we need to check if a default attribute node should be created and inserted into the tree
            XmlAttribute defattr = parent.OwnerDocument.GetDefaultAttribute( (XmlElement)parent, retNode.Prefix, retNode.LocalName, retNode.NamespaceURI );
            if ( defattr != null )
                InsertNodeAt( i, defattr );
            return retNode;
        }

        internal void Detach( XmlAttribute attr ) {
            attr.OwnerElement.Attributes.Remove( attr );
        }

        //insert the parent element node into the map
        internal void InsertParentIntoElementIdAttrMap(XmlAttribute attr)
        {
            XmlElement parentElem = parent as XmlElement;
            if (parentElem != null)
            {
                if (parent.OwnerDocument == null)
                    return;
                XmlName attrname = parent.OwnerDocument.GetIDInfoByElement(parentElem.XmlName);
                if (attrname != null && attrname.Prefix == attr.XmlName.Prefix && attrname.LocalName == attr.XmlName.LocalName) {
                    parent.OwnerDocument.AddElementWithId(attr.Value, parentElem); //add the element into the hashtable
                }
            }
        }

        //remove the parent element node from the map when the ID attribute is removed
        internal void RemoveParentFromElementIdAttrMap(XmlAttribute attr)
        {
            XmlElement parentElem = parent as XmlElement;
            if (parentElem != null)
            {
                if (parent.OwnerDocument == null)
                    return;
                XmlName attrname = parent.OwnerDocument.GetIDInfoByElement(parentElem.XmlName);
                if (attrname != null && attrname.Prefix == attr.XmlName.Prefix && attrname.LocalName == attr.XmlName.LocalName) {
                    parent.OwnerDocument.RemoveElementWithId(attr.Value, parentElem); //remove the element from the hashtable
                }
            }
        }

        //the function checks if there is already node with the same name existing in the collection
        // if so, remove it because the new one will be inserted to replace this one (could be in different position though ) 
        //  by the calling function later
        internal int RemoveDuplicateAttribute( XmlAttribute attr ) {
            int ind = FindNodeOffset( attr.LocalName, attr.NamespaceURI );
            if ( ind != -1 ) {
                XmlAttribute at = (XmlAttribute)nodes[ind];
                base.RemoveNodeAt( ind );                
                RemoveParentFromElementIdAttrMap( at );
            }
            return ind;
        }

        internal bool PrepareParentInElementIdAttrMap(string attrPrefix, string attrLocalName) {
            XmlElement parentElem = parent as XmlElement;
            Debug.Assert( parentElem != null );
            XmlDocument doc = parent.OwnerDocument;
            Debug.Assert( doc != null );
            //The returned attrname if not null is the name with namespaceURI being set to string.Empty
            //Because DTD doesn't support namespaceURI so all comparisons are based on no namespaceURI (string.Empty);
            XmlName attrname = doc.GetIDInfoByElement(parentElem.XmlName);
            if (attrname != null && attrname.Prefix == attrPrefix && attrname.LocalName == attrLocalName) {
                return true;
            }
            return false;
        }

        internal void ResetParentInElementIdAttrMap(string oldVal, string newVal) {
            XmlElement parentElem = parent as XmlElement;
            Debug.Assert( parentElem != null );
            XmlDocument doc = parent.OwnerDocument;
            Debug.Assert( doc != null );
            doc.RemoveElementWithId(oldVal, parentElem); //add the element into the hashtable
            doc.AddElementWithId(newVal, parentElem);
        }

        // WARNING: 
        //  For performance reasons, this function does not check
        //  for xml attributes within the collection with the same full name.
        //  This means that any caller of this function must be sure that
        //  a duplicate attribute does not exist.
        internal XmlAttribute InternalAppendAttribute( XmlAttribute node ) {
            // a duplicate node better not exist
            Debug.Assert( -1 == FindNodeOffset( node ));    

            XmlNode retNode = base.AddNode( node );
            Debug.Assert( retNode is XmlAttribute );
            InsertParentIntoElementIdAttrMap( (XmlAttribute) node );
            return (XmlAttribute)retNode;
        }
    }
}
